<?php
/*******************************************************************************
auth.inc.php
Authentication layer to be included in all pages with secure content

Created by Dmitriy Panteleyev (dpantel@emory.edu)
Modified by NCSU Libraries, NC State University. Modifications by Karl Doerr, Troy Hurteau, and Adam Constabaris (libraries.opensource@ncsu.edu).

This file is part of NCSU's distribution of ReservesDirect. This version has not been downloaded from Emory University
or the original developers of ReservesDirect. Neither Emory University nor the original developers of ReservesDirect have authorized
or otherwise endorsed or approved this distribution of the software.

Copyright (c) 2004-2006 Emory University, Atlanta, Georgia.

Licensed under the NCSU ReservesDirect License, Version 2.0 (the "License"); 
you may not use this file except in compliance with the License. You may obtain a copy of the full License at
 http://www.lib.ncsu.edu/it/opensource/

ReservesDirect is distributed in the hope that it will be useful,
but is distributed "AS IS" and WITHOUT ANY WARRANTY, without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE, and without any warranty as to non-infringement of any third
party's rights. See the License for the specific language governing permissions and limitations under the License.

The original version of ReservesDirect is located at:
http://www.reservesdirect.org/

This version of ReservesDirect, distributed by NCSU, is located at:
http://code.google.com/p/reservesdirect-ncsu/

*******************************************************************************/


/** 
 * @desc Attempt to authenticate a user who is logging in (username/pass in request) or reauthenticate user has already logged in.  On success: initializes a global user object ($u) for the proper user class.  On failure: redirects to login page.
 */


require_once('secure/classes/user.class.php');
require_once('secure/classes/users.class.php');
require_once('secure/classes/ldapAuthN.class.php');
require_once('secure/lib/Rd/CodeLookup.php');
require_once('secure/lib/Rd/UrlRewrite.php');

//start or resume session
if (session_id()=='') {
	session_start();
}

try{
	if (array_key_exists('username',$_REQUEST) && array_key_exists('pwd', $_REQUEST)) {
		switch($g_authenticationType) {
			case 'LDAP':
				authByLDAP($_REQUEST['username'], $_REQUEST['pwd']);		
			break;
			
			case 'StandAlone':
				authByDB($_REQUEST['username'], $_REQUEST['pwd']);
			break;
			
			case 'Custom':
				authByCustom($_REQUEST['username'], $_REQUEST['pwd']);
			break;
			
			default:
				authByAny($_REQUEST['username'], $_REQUEST['pwd']);
		}
	}
} catch (Exception $e){
	$login_error = $e->getMessage();
	include("destroySession.inc.php");	//destroy any current session data (just in case)
	require_once(APPLICATION_PATH . '/login.php');	//show login page	
	exit;
}

if(!empty($_SESSION['username']) && !empty($_SESSION['userclass'])) {
	//if the session has the username and the userclass defined,
	//then we consider this user authenticated
	//initialize the GLOBAL user object
	$usersObject = new users();
	$u = $usersObject->initUser($_SESSION['userclass'], $_SESSION['username']);	
} else {	//user is not authenticated; show login page (+ error msg if needed)
	include("destroySession.inc.php");	//destroy any current session data (just in case)
	try{
		$code = array_key_exists('failureCode',$_REQUEST)? substr($_REQUEST['failureCode'],0, 5) : '';
		$login_error = 
			array_key_exists('username', $_REQUEST)
			? 'Login failed, unknown reason.' //flag as login error if user provided a username and login has not already happened.
			: (
				array_key_exists('failureCode', $_REQUEST)
				? Rd_CodeLookup::getMessage('loginFailure', $code)
				: ''
			);
	} catch (Exception $e){
		$login_error = "Code Lookup Error: No data for status code for \"loginFailure:{$code}\".";
	}
	require_once(APPLICATION_PATH . '/login.php');
	die;
}


/**
 * @return void
 * @param boolean $authenticated
 * @param user obj $user (optional)
 * @desc If $authenticated is TRUE and $user is initialized, then sets the session vars that will be later used for quick re-authentication;  If $authenticated is FALSE, then unsets those session vars.
 */
function setAuthSession($authenticated, $user = null, $failureCode = '000') {
	if($authenticated && ($user instanceof user)) {
		//since the user's role/auth-status is changing, change session ID (attempt to prevent session fixing)
		session_regenerate_id();
		$_SESSION['username'] = $user->getUsername();
		$_SESSION['userclass'] = $user->getUserClass();
		$user->setLastLogin();	//mark user's last login
		header("Location: {$_SERVER['REQUEST_URI']}");
		die('Login successful, redirecting you to your destination.');
	} else {	//not authenticated, unset all auth session vars
		$_SESSION['username'] = null;
		$_SESSION['userclass'] = null;
		$scrubbedUrl = Rd_UrlRewrite::scrubGet($_SERVER['REQUEST_URI'],$_GET, array('exclude'=>array('failureCode')));
		$failureCode = substr($failureCode,0, 5);
		$failureMessage = Rd_CodeLookup::getMessage('loginFailure', $failureCode, 'Unknown Failure Code: ' . $failureCode);
		$failureParam = (
			(strpos( $scrubbedUrl,'?') === false ? '?' : '&')
			. "failureCode={$failureCode}"
		);
		header("Location: {$scrubbedUrl}{$failureParam}");
		die("Login failed, {$failureMessage}");
	}
}


/**
 * @return boolean
 * @param string $username Username
 * @param string $password Password
 * @desc Attempt to auth against LDAP. Returns true on success, and sets $_SESSION['username'], $_SESSION['userclass'];  Returns false on failure, and sets both session vars to null;
 	NOTE: requires array of ldap info as $g_ldap;
 */
function authByLDAP($username, $password) {
	global $g_ldap;
	$ldap = new ldapAuthN();
	$user = new user();
	
	//try to authenticate against ldap
	if($ldap->auth($username, $password)) {
		//passed authentication, try to get user from DB
		if(!$user->getUserByUserName($username)) {	//if user record not found in our DB, attempt to create one
			//get directory info
			$ldap->search();
			$user_info = $ldap->getUserInfo();			
			//create a new record with directory info
			//(LDAP returns username in caps, so strtolower() it)
			$resultUsername = (
				array_key_exists($g_ldap['firstname'], $user_info)
				&& is_array($user_info[$g_ldap['firstname']]) 
					&& '' != trim($user_info[$g_ldap['canonicalName']][0]) 
				? trim(strtolower($user_info[$g_ldap['canonicalName']][0]))
				: addslashes(substr(trim(strtolower($username)),0,12))
			);
			$resultFirstname = (
				array_key_exists($g_ldap['firstname'], $user_info) 
					&& is_array($user_info[$g_ldap['firstname']]) 
				? trim($user_info[$g_ldap['firstname']][0])
				: ''
			);
			$resultLastname = (
				array_key_exists($g_ldap['lastname'], $user_info) 
					&& is_array($user_info[$g_ldap['lastname']]) 
				? trim($user_info[$g_ldap['lastname']][0])
				: ''
			);
			$resultEmail = (
				array_key_exists($g_ldap['email'], $user_info) 
					&& is_array($user_info[$g_ldap['email']]) 
				? trim(strtolower($user_info[$g_ldap['email']][0]))
				: ''
			);
			$user->createUser($resultUsername, $resultFirstname, $resultLastname, $resultEmail, 0);
		}
				
		//user is now authenticated, set the session vars
		setAuthSession(true, $user);
		//return true; #execution will end above
	} else {
		//unset these
		//setAuthSession(false, null, '002');
		if (-1 == $ldap->getErrorNumber()) {
			setAuthSession(false, null, '002');
			//throw new Exception('Unable to contact the authentication service.');
		}
		return false;
	}
}


/**
 * @return boolean
 * @param string $username Username
 * @param string $password Password
 * @desc Attempt to auth against local database. Returns true on success, and sets $_SESSION['username'], $_SESSION['userclass'];  Returns false on failure, and sets both session vars to null;
 */
function authByDB($username, $password) {
	$user = new user();
	//attempt to authenticate user against our database
	if($user->getUserByUserName_Pwd($username, md5($password))) {
		//user is now authenticated, set the session vars
		setAuthSession(true, $user);
		//return true; #execution will end above...
	} else {
		//unset the session vars
		setAuthSession(false, null, '003');
		//return false; #execution will end above...
	}
}


/**
 * @return boolean
 * @param string $username Username
 * @param string $password Password
 * @desc Attempt to auth against LDAP, NT Domain, & local DB (in that order). Returns true on success, and sets $_SESSION['username'], $_SESSION['userclass'];  Returns false on failure, and sets both session vars to null;
 */
function authByAny($username, $password) {
	try{
		if(authByLDAP($username, $password)) {
			return true;
		} 
	}catch (Exception $e){
		$dbAuth = authByDB($username, $password);
		if($dbAuth){
			return true;
		} else {
			throw $e;
		}
	}/*
	if(authByCustom($username, $password)) {	//try custom
		return true;	//success
	} else {
	*/
	return authByDB($username, $password);	//try standalone db
	
}

/**
 * @return boolean
 * @param string $username Username
 * @param string $password Password
 * @desc Attempt to auth against LDAP. Returns true on success, and sets $_SESSION['username'], $_SESSION['userclass'];  Returns false on failure, and sets both session vars to null;
 * 
 * This is a custom LDAP authentication function specficially created for authenticating with the NCSU LDAP server.
 */
function authByCustom($username, $password) {	
	$user = new user();
	global $g_ldapServiceUrl, $g_ldap;
	
	$soapClientUrl = $g_ldapServiceUrl;
	// connect to ldap web service
    try {
    	$client = new SoapClient($soapClientUrl);
    } catch (Exception $e) {
    	throw new Exception('Unable to contact the authentication service.');
    }
    
    // build authentication parameters
    $params = array(
            'Username'=>'uid=' . $username . ',' . $g_ldap['accountbasedn'],
            'Password'=>$password,
            'URL'=>"ldaps://{$g_ldap['domain']}:{$g_ldap['port']}"
    );
    
    // attempt authentication
    $result = $client->Authentication($params);
    
    if($result->ValidCredentials == 1) {    		
    		//passed authentication, try to get user from DB
			if(!$user->getUserByUserName($username)) {	//if user record not found in our DB, attempt to create one
				$userInfo = array();
				ncsuldapUIDLookup(strtolower($username), $userInfo);
				$user->createUser($userInfo['uid'], $userInfo['givenName'], $userInfo['sn'], $userInfo['ncsuPrimaryEMail'], 0);
			}
					
			//user is now authenticated, set the session vars
			setAuthSession(true, $user);
			//return true;	#auth session will end above
    } else {
            //unset these
			setAuthSession(false, null, '004');
			//return false; #auth session will end above
    }
}

function ncsuldapUIDLookup($uid, &$userInfo, $doMerge=TRUE){
	global $g_ldap;
	$userInfo = array();

	// server/context definitions
	define("NCSU_LDAP_SERVER",$g_ldap['domain']);
	define("NCSU_LDAP_CONTEXT",$g_ldap['basedn']);

	$ldapConnect = ldap_connect(NCSU_LDAP_SERVER);		
	ldap_bind($ldapConnect); // a. nony mous
	$context = NCSU_LDAP_CONTEXT;
	$searchstring = "uid=".$uid;
	$searchResult = ldap_search($ldapConnect,$context,$searchstring,array("*","+"));
	$userInfo['uid'] = $uid;
	$userInfo['info']['has_account'] = FALSE;
	$accountInfo = array();
	$userInfo['info']['is_employee'] = FALSE;
	$employeeInfo = array();
	$userInfo['info']['is_student'] = FALSE;
	$studentInfo = array();
	for(
		$entryID = ldap_first_entry($ldapConnect,$searchResult);
		$entryID != FALSE;
		$entryID = ldap_next_entry($ldapConnect,$entryID)
	){
		$thisEntry = array();
		$thisDN = '';
		$thisDN = ldap_get_dn($ldapConnect,$entryID);
		$thisEntry = ldap_get_attributes($ldapConnect,$entryID);
		
		if(!(isset($thisEntry))) continue;
		
		// parse dn
		$dnarray = explode(',',$thisDN);
		$checkou = $dnarray[1];
		switch($checkou) {
			
			case "ou=accounts":
				$userInfo['info']['has_account'] = TRUE;
				$dataInfo = &$accountInfo;
				break;
				
			case "ou=employees":
				$userInfo['info']['is_employee'] = TRUE;
				$dataInfo = &$employeeInfo;
				break;	
				
			case "ou=students":		
				$userInfo['info']['is_student'] = TRUE;
				$dataInfo = &$studentInfo;
				break;					
		
			// not dealing with a group/printer/host/other
			// somehow (don't know how) keyed by identifier uid=$uid...
			default:
				continue 2;
		}
		
		foreach($thisEntry as $attribute => $value) {
			if(!(is_array($value))) continue;
			if($attribute == "uid") continue;
			if($attribute == "count") continue;
			
			if($value['count'] > 1) {
				$dataInfo[$attribute] = $value;
				unset($dataInfo[$attribute]['count']);
			}
			else {
				$dataInfo[$attribute] = $value[0];	
			}
		}	
	}
	// merge information student, then employee, then account

	if($userInfo['info']['is_student']) {
		if($doMerge) $userInfo = array_merge($userInfo,$studentInfo);
		$userInfo['info']['student'] = $studentInfo;
	}

	if($userInfo['info']['is_employee']) {
		if($doMerge) $userInfo = array_merge($userInfo,$employeeInfo);
		$userInfo['info']['employee'] = $employeeInfo;
	}
			
	if($userInfo['info']['has_account']) {
		if($doMerge) $userInfo = array_merge($userInfo,$accountInfo);
		$userInfo['info']['account'] = $accountInfo;
	}
		
	if($doMerge) {
		// merged values we don't care about:
		$noMergeAttribs = array('objectClass',
			 'structuralObjectClass',
			 'entryUUID',
			 'creatorsName',
			 'createTimestamp',
			 'modifyTimestamp',
			 'subschemaSubentry',
			 'hasSubordinates',
			 'modifiersName',
			 'entryCSN'
		);

		foreach($noMergeAttribs as $attribute) {
			unset($userInfo[$attribute]);
		}
	}

	return true;
}

/**
 * @return boolean
 * @param string $qs QueryString data
 * @desc Attempt to auth from external system.  Compare passed values against secret key
 * 		$qs key values: u username, 
 * 						sys  external system identifier
 * 						t	timestamp  seconds since ‘00:00:00 1970-01-01 UTC’
 * 						key md5 of concatenation of above
 */
function authBySecretKey($qs_data) {
	if (!is_null($qs_data)) {
		global $g_trusted_systems, $g_permission;
	
	if (is_null($qs_data)) return false;
		
		parse_str(base64_decode($qs_data), $auth_data);
		
		$trusted_system_key = $g_trusted_systems[$auth_data['sys']]['secret'];
		$timeout = $g_trusted_systems[$auth_data['sys']]['timeout'];
	
		$timestamp = new DateTime($auth_data['t']);
		$expire = new DateTime(time());
		$expire->modify("+$timeout minutes");
	
		if ($timestamp >  $expire)
			return false; //encoded timestamp is too old
		else {
			$user = new user();
		
			if ($user->getUserByUserName($auth_data['u']) == false || $user->getRole() > $g_permission['instructor'])
				return false;	//do not allow privileged users access without login
		
			$verification = $auth_data['u'] . $auth_data['t'];
			
			$verification .= $auth_data['sys'];
			$verification .= $trusted_system_key;			
			
			if (hash("sha256", $verification) == $auth_data['key'])
			{
				setAuthSession(true, $user);
				//return true; #will terminate above
			}
		}
	}
	
	return false;
}

